---
id: specification-document
title: 5. 规范化接口文档
sidebar_label: 5. 规范化接口文档
---

import useBaseUrl from "@docusaurus/useBaseUrl";
import Tabs from "@theme/Tabs";
import TabItem from "@theme/TabItem";

## 5.1 什么是接口文档

在现在移动为王、多端互辅、前端百花齐放的开放时代，不再是一人包揽式开发，大家各司其职，后端工程师负责接口开发，前端负责接口联调，也就是互联网现在流行的前后端分离架构。

所以就需要由前后端工程师共同定义接口，编写接口文档，之后大家按照这个接口文档进行开发、维护及开放给第三方。

## 5.2 为什么要写接口文档

- 能够让前端开发与后台开发人员更好的配合，提高工作效率
- 项目迭代或者项目人员更迭时，方便后期人员查看和维护
- 方便测试人员进行接口测试

## 5.3 为什么需要规范化文档

由于每个公司后端人员技术参差不齐，技术文档能力也不例外，导致接口定义及文档五花八门，不同项目或不同公司对接极其困难，而且体验糟糕。所以，无规矩不成方圆，为了开发人员间更好的配合，迫切需要整理出一套规范。

通常接口规范分为六个部分：

### 5.3.1 协议规范

为了确保不同系统/模块间的数据交互，需要事先约定好通讯协议，如：TCP、HTTP、HTTPS 协议。为了确保数据交互安全，建议使用 HTTPS 协议

### 5.3.2 接口路径规范

作为接口路径，为了方便清晰的区分来自不同的系统，可以采用不同系统/模块名作为接口路径前缀，如：支付模块：`/pay/xxx`，订单模块：`/order/xxx`

### 5.3.3 版本控制规范

为了便于后期接口的升级和维护，建议在接口路径中加入版本号，便于管理，实现接口多版本的可维护性。如：接口路径中添加类似"`v1`"、"`v2`"等版本号

### 5.3.4 接口命名规范

和 C# 命名规范一样，好的、统一的接口命名规范，不仅可以增强其可读性，而且还会减少很多不必要的口头/书面上的解释。，可使用"驼峰命名法"按照实现接口的**业务类型、业务场景**等命名，有必要时可采取多级目录命名，但目录不宜过长，两级目录较为适宜

- `常见命名方式`:
  - `接口名称动词前/后缀化`： 接口名称以接口数据操作的动词为前/后缀，常见动词有：`Add、Delete、Update、Query、Get、Send、Save、Detail、List`等，如：新建用户 `AddUser`、查询订单详情 `QueryOrderDetail`。
  - `接口名称动词 + 请求方式`：接口路径中包含具体接口名称的名词，接口数据操作动作以 HTTP 请求方式来区分。常用的 HTTP 请求方式有：
    - `GET`：从服务器取出资源（一项或多项）
    - `POST`：在服务器新建一个资源
    - `PUT`：在服务器更新资源（客户端提供改变后的完整资源）
    - `PATCH`：在服务器更新资源（客户端提供改变的属性）
    - `DELETE`：从服务器删除资源

### 5.3.5 请求参数规范

- `请求方式`：按照 `GET、POST、PUT` 等含义定义，避免出现不一致现象，对人造成误解、歧义
  - `请求头`：请求头根据项目需求添加配置参数。如：请求数据格式，`accept=application/json` 等。如有需要，请求头可根据项目需求要求传入用户 token、唯一验签码等加密数据
  - `请求参数/请求体`： 请求参数字段，尽可能与数据库表字段、对象属性名等保持一致，因为保持一致最省事，最舒服的一件事

### 5.3.6 返回数据规范

统一规范返回数据的格式，对己对彼都有好处，此处以 json 格式为例。返回数据应包含：**返回状态码、返回状态信息、具体数据**。**返回数据中的状态码、状态信息，常指具体的业务状态，不建议和 HTTP 状态码混在一起**。HTTP 状态，是用来体现 HTTP 链路状态情况，如：404-Not Found。HTTP 状态码和 json 结果中的状态码，并存尚可，用于体现不同维度的状态。

## 5.4 什么是 Swagger

相信无论是前端还是后端开发，都或多或少地被接口文档折磨过。前端经常抱怨后端给的接口文档与实际情况不一致。后端又觉得编写及维护接口文档会耗费不少精力，经常来不及更新。

其实无论是前端调用后端，还是后端调用后端，都期望有一个好的接口文档。但是这个接口文档对于程序员来说，就跟注释一样，经常会抱怨别人写的代码没有写注释，然而自己写起代码起来，最讨厌的，也是写注释。所以仅仅只通过强制来规范大家是不够的，随着时间推移，版本迭代，接口文档往往很容易就跟不上代码了。

**发现了痛点就要去找解决方案。解决方案用的人多了，就成了标准的规范，这就是 `Swagger` 的由来**。

通过这套规范，你只需要按照它的规范去定义接口及接口相关的信息。再通过 `Swagger` 衍生出来的一系列项目和工具，就可以做到生成各种格式的接口文档，生成多种语言的客户端和服务端的代码，以及在线接口调试页面等等。

这样，如果按照新的开发模式，在开发新版本或者迭代版本的时候，只需要更新 `Swagger` 描述文件，就可以自动生成接口文档和客户端服务端代码，做到调用端代码、服务端代码以及接口文档的一致性。

所以，Swagger 是一个规范和完整的框架，用于生成、描述、调用和可视化`RESTful` 风格的 `Web` 服务。

总体目标是使客户端和文件系统作为服务器以同样的速度来更新。文件的方法、参数和模型紧密集成到服务器端的代码，允许 API 来始终保持同步。`Swagger` 让部署管理和使用功能强大的 `API` 从未如此简单。

## 5.5 Swagger 使用

`Fur` 框架提供了非常方便且灵活的 `Swagger` 配置，无需增加额外学习成本。

### 5.5.1 注册服务

```cs {4,13-16,18,30-33} title="Fur.Web.Entry/Startup.cs"
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace Fur.Web.Entry
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddApp(options =>
            {
                options.AddSpecificationDocuments();
            });

            services.AddControllers().AddDynamicApiControllers();

            // Other Codes...
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseApp(options =>
            {
                options.UseSpecificationDocuments();
            });

            // Other Codes...
        }
    }
}
```

:::tip 小知识

`services.AddSpecificationDocuments()` 通常和 `.AddDynamicApiControllers()` 成对出现。

:::

### 5.5.2 默认地址

在 `Fur` 框架中，默认 `规范化文档` 地址为主机根目录，**支持自定义配置**。

如下图所示：

<img src={useBaseUrl("img/swagger1.png")} />

### 5.5.3 默认分组

`Fur` 框架中默认分组名为 `Default`，**支持自定义配置**。

### 5.5.4 文档注释

规范化文档默认扫描 `Fur.Application`，`Fur.Web.Core`，`Fur.Web.Entry` 三个程序集`.xml` 注释文件，**支持自定义配置**。

只支持 `///` 标识的注释语法，如：**类、方法、属性、参数、返回值、验证特性**。

```cs {5-7,10-13,19-23}
using Fur.DynamicApiController;

namespace Fur.Application
{
    /// <summary>
    /// 类注释
    /// </summary>
    public class FurAppService : IDynamicApiController
    {
        /// <summary>
        /// 方法注释
        /// </summary>
        /// <returns></returns>
        public string Get()
        {
            return nameof(Fur);
        }

        /// <summary>
        /// 带 ID 参数的方法注释
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public int Get(int id)
        {
            return id;
        }
    }
}
```

如下图所示：

<img src={useBaseUrl("img/swagger2.png")} />

### 5.5.5 多分组支持

多分组是一个系统中必备功能，我们可以将系统划分为多个模块，每个模块都独立的 `api` 配置。在 `Fur` 框架中，实现多分组非常简单。如：

```cs {5,21,32}
using Fur.DynamicApiController;

namespace Fur.Application
{
    [ApiDescriptionSettings("Group1")]
    public class FurAppService : IDynamicApiController
    {
        /// <summary>
        /// 随父类 Group1 分组
        /// </summary>
        /// <returns></returns>
        public string Post()
        {
            return nameof(Fur);
        }

        /// <summary>
        /// 在 Group1、Group3 都有我
        /// </summary>
        /// <returns></returns>
        [ApiDescriptionSettings("Group1", "Group3")]
        public string Get()
        {
            return nameof(Fur);
        }

        /// <summary>
        /// 我只在 Group2 出现
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        [ApiDescriptionSettings("Group2")]
        public int Get(int id)
        {
            return id;
        }
    }
}
```

如下图所示：

<img src={useBaseUrl("img/swagger3.gif")} />

### 5.5.6 多分组排序

<Tabs
  defaultValue="fz1"
  values={[
    { label: "方式一", value: "fz1" },
    { label: "方式二", value: "fz2" },
  ]}
>
  <TabItem value="fz1">


**通过分组名称添加 `@整数` 进行排序**

```cs {5,19}
using Fur.DynamicApiController;

namespace Fur.Application
{
    [ApiDescriptionSettings("Group1@1")]
    public class FurAppService : IDynamicApiController
    {
        public string Post()
        {
            return nameof(Fur);
        }

        [ApiDescriptionSettings("Group1", "Group3")]
        public string Get()
        {
            return nameof(Fur);
        }

        [ApiDescriptionSettings("Group@2")]
        public int Get(int id),19
        {
            return id;
        }
    }
}
```

可以通过在分组名后面添加 `@整数` 进行排序，`整数` 越大排前面。如果分组名称多次指定且多次指定了 `@整数` ，则自动**取该分组最大的整数**进行排序。

  </TabItem>
  <TabItem value="fz2">


**通过配置文件配置排序**

```json {2,3,4-17} title="Fur.Web.Entry/appsettings.json"
{
  "AppSettings": {
    "SpecificationDocumentSettings": {
      "GroupOpenApiInfos": [
        {
          "Group": "Group1",
          "Order": 1
        },
        {
          "Group": "Group2",
          "Order": 2
        },
        {
          "Group": "Group3",
          "Order": 0
        }
      ]
    }
  }
}
```

  </TabItem>
</Tabs>


如下图所示：

<img src={useBaseUrl("img/swagger5.png")} />

:::tip 排序说明

分组默认排序 `Order` 为 `0`。如果同时配置了 `@整数` 和 `appsettings.json` 配置文件，那么优先采用 `appsettings.json` 中的 `Order`

:::

### 5.5.7 多分组信息配置

`Fur` 框架提供了可通过 `appsetting.json` 配置分组信息：

```json {3,4-20} title="Fur.Web.Entry/appsettings.json"
{
  "AppSettings": {
    "SpecificationDocumentSettings": {
      "GroupOpenApiInfos": [
        {
          "Group": "Group1",
          "Title": "分组标题",
          "Description": "这里是分组描述",
          "Version": "版本号",
          "TermsOfService": "https://furos.cn",
          "Contact": {
            "Name": "百小僧",
            "Url": "https://gitee.com/monksoul",
            "Email": "monksoul@outlook.com"
          },
          "License": {
            "Name": "Apache-2.0",
            "Url": "https://gitee.com/monksoul/Fur/blob/alpha/LICENSE"
          }
        }
      ]
    }
  }
}
```

如下图所示：

<img src={useBaseUrl("img/swagger5.png")} />

### 5.5.8 组中组（标签）

`Tag` 配置主要用于配置 `Swagger` 标签分组信息及合并标签。也就是 `组中组`:

<Tabs
  defaultValue="tag1"
  values={[
    { label: "标签命名", value: "tag1" },
    { label: "合并标签", value: "tag2" },
  ]}
>
  <TabItem value="tag1">


#### 未贴标签之前

```cs
using Fur.DynamicApiController;

namespace Fur.Application
{
    public class FurAppService : IDynamicApiController
    {
        public string Get()
        {
            return nameof(Fur);
        }

        public int Get(int id)
        {
            return id;
        }
    }

    public class TestAppService : IDynamicApiController
    {
        public string Get()
        {
            return nameof(Fur);
        }

        public int Get(int id)
        {
            return id;
        }
    }
}
```

#### 贴标签之后

```cs {5,19}
using Fur.DynamicApiController;

namespace Fur.Application
{
    [ApiDescriptionSettings(Tag = "分组一")]
    public class FurAppService : IDynamicApiController
    {
        public string Get()
        {
            return nameof(Fur);
        }

        public int Get(int id)
        {
            return id;
        }
    }

    [ApiDescriptionSettings(Tag = "分组二")]
    public class TestAppService : IDynamicApiController
    {
        public string Get()
        {
            return nameof(Fur);
        }

        public int Get(int id)
        {
            return id;
        }
    }
}
```

如下图所示：

<img src={useBaseUrl("img/tag1.png")} />

  </TabItem>
  <TabItem value="tag2">


```cs {5,19}
using Fur.DynamicApiController;

namespace Fur.Application
{
    [ApiDescriptionSettings(Tag = "合并所有标签")]
    public class FurAppService : IDynamicApiController
    {
        public string Get()
        {
            return nameof(Fur);
        }

        public int Get(int id)
        {
            return id;
        }
    }

    [ApiDescriptionSettings(Tag = "合并所有标签")]
    public class TestAppService : IDynamicApiController
    {
        public string Get()
        {
            return nameof(Fur);
        }

        public int Get(int id)
        {
            return id;
        }
    }
}
```

如下图所示：

<img src={useBaseUrl("img/tag2.png")} />

  </TabItem>
</Tabs>


:::tip 小知识

如果 `Tag` 名字一样，则会自动合并，否则只是命名。

:::

### 5.5.9 默认展开所有文档

```json {2-5} title="Fur.Web.Entry/appsettings.json"
{
  "AppSettings": {
    "SpecificationDocumentSettings": {
      "DocExpansionState": "Full"
    }
  }
}
```

如下图所示：

<img src={useBaseUrl("img/swagger6.gif")} />

`DocExpansionState` 配置说明：

- `List`：列表式（展开子类），**默认值**
- `Full`：完全展开
- `None`：列表式（不展开子类）

### 5.5.10 配置文档标题

```json {2-5} title="Fur.Web.Entry/appsettings.json"
{
  "AppSettings": {
    "SpecificationDocumentSettings": {
      "DocumentTitle": "我是自定义标题"
    }
  }
}
```

如下图所示：

<img src={useBaseUrl("img/swagger7.png")} />

### 5.5.11 授权控制

待整理...

```json title="Fur.Web.Entry/appsettings.json"
{
  "AppSettings": {
    "SpecificationDocumentSettings": {
      "EnableAuthorized": true,

      "SecurityDefinitions": [
        {
          "Id": "Bearer",
          "Type": "Http",
          "Name": "Authorization",
          "Description": "JWT Authorization header using the Bearer scheme.",
          "BearerFormat": "JWT",
          "Scheme": "bearer",
          "In": "Header",

          "Requirement": {
            "Scheme": {
              "Reference": {
                "Id": "Bearer",
                "Type": "SecurityScheme"
              },
              "Accesses": null
            }
          }
        }
      ]
    }
  }
}
```

### 5.5.12 在线测试

如下图所示：

<img src={useBaseUrl("img/swagger8.gif")} />

### 5.5.13 性能监视 `MiniProfiler`

规范化文档默认集成了 `MiniProfiler` 第三方性能组件，通过该组件可以方便查看请求性能、异常堆栈、数据库操作等信息。默认在 `Swagger` 首页左上角显示。

如下图所示：

<img src={useBaseUrl("img/mipr.png")} />

:::note 小提示

也可以通过 **`appsetting.json`** 中 **`AppSettings:InjectMiniProfiler`** 设为 **`false`** 关闭。

:::

### 5.5.14 定义接口输出类型

```cs {2,8-9}
using Fur.DynamicApiController;
using Microsoft.AspNetCore.Mvc;

namespace Fur.Application
{
    public class FurAppService : IDynamicApiController
    {
        [ProducesResponseType(201, Type = typeof(TestDto))]
        [ProducesResponseType(400)]
        public string Get()
        {
            return nameof(Fur);
        }
    }
}
```

如下图所示：

<img src={useBaseUrl("img/fhzlx.png")} />

## 5.6 `SpecificationDocumentSettings` 配置

除了上述例子外，`Fur` 提供了一些配置选项，如：

- `DocumentTitle`：文档标题，`string`，默认 `Specification Api Document`
- `DefaultGroupName`：默认分组名，`string`，默认 `Default`
- `EnableAuthorized`：是否启用权限控制，`bool`，默认 `true`
- `FormatAsV2`：采用 `Swagger 2.0` 版本，`bool`，默认 `false`
- `RoutePrefix`：规范化文档地址，`string`，默认 `string`
- `DocExpansionState`：文档显示方式，`DocExpansion`，默认 `List`，取值：
  - `List`：列表式（展开子类），**默认值**
  - `Full`：完全展开
  - `None`：列表式（不展开子类）
- `XmlComments`：程序集注释描述文件名（可带 `.xml`，`string`，默认 `Fur.Application, Fur.Web.Entry, Fur.Web.Core`
- `GroupOpenApiInfos`：分组信息配置，`SpecificationOpenApiInfo[]`，默认 `{ 'Group': 'Default'}`
- `SecurityDefinitions`：安全策略定义配置，`SpecificationOpenApiSecurityScheme[]`，默认 `[]`

## 5.7 反馈与建议

:::note 与我们交流

给 Fur 提 [Issue](https://gitee.com/monksoul/Fur/issues/new?issue)。

:::
